#include <stdio.h>
#include <malloc.h>
#include <pthread.h>

/* The following structure contains the necessary information
to allow the function "dotprod" to access its input data and
place its output so that it can be accessed later. */

typedef struct {
    double *a;
    double *b;
    double sum;
    int len;
} DotData;

typedef struct {
    int tindex;
    int tcount;
    int tslice;
    pthread_mutex_t* mut;
} DotParams;

#define VECLEN (1<<23)

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
DotData data;

/* We will use a function (dotprod) to perform the scalar product.
All input to this routine is obtained through a structure of
type DotData and all output from this function is written into
this same structure.  While this is unnecessarily restrictive
for a sequential program, it will turn out to be useful when
we modify the program to compute in parallel. */
void* dotprod(void* p)
{
    int r, i, j, k;
    double mysum, *x, *y;
    DotParams* params = (DotParams*) p;

    x = data.a;
    y = data.b;

    /* Perform the dot product and assign result
    to the appropriate variable in the structure. */
    mysum = 0;
    int start = (data.len / params->tcount) * params->tindex;
    int end = (data.len / params->tcount) * (params->tindex + 1);
    for (r = 0; r < 100; r++) {
        /* A */
        // for (i = params->tindex; i < data.len; i += params->tcount) {
        //     mysum += (x[i] / y[i]);
        // }
        /* B */
        // for (j = params->tindex; j < data.len; j += params->tslice * params->tcount) {
        //     for (i = 0; i < params->tslice; i++) {
        //         mysum += (x[j+i] / y[j+i]);
        //     }
        // }
        /* C */
        for (i = start; i < end ; i++) {
            mysum += (x[i] / y[i]);
        }
    }
    pthread_mutex_lock(params->mut);
    data.sum += mysum;
    pthread_mutex_unlock(params->mut);
    pthread_exit(NULL);
}


/* The main program initializes data and calls the dotprd() function.
Finally, it prints the result. */
int main (int argc, char* argv[])
{
    int i, t, rc, threads, slices;
    double *a, *b;
    pthread_t* pthreads;
    DotParams* params;

    threads = atoi(argv[1]);
    slices = atoi(argv[2]);

    /* Assign storage and initialize values */
    a = (double*) malloc(VECLEN*sizeof(double));
    b = (double*) malloc(VECLEN*sizeof(double));

    for (i = 0; i < VECLEN; i++) {
        a[i] = 3;
        b[i] = a[i];
    }

    data.len = VECLEN;
    data.a = a;
    data.b = b;
    data.sum = 0;

    pthreads = (pthread_t*) malloc(threads*sizeof(pthread_t));
    params = (DotParams*) malloc(threads*sizeof(DotParams));

    /* Perform the  dotproduct */
    for (t = 0; t < threads; t++) {
        params[t].tindex = t;
        params[t].tcount = threads;
        params[t].tslice = slices;
        params[t].mut = &mut;
        rc = pthread_create(&pthreads[t], NULL, dotprod, (void*) &(params[t]));
        if (rc) { printf("ERORR return code is %d\n", rc); exit(-1); }
    }

    /* Wait for the threads */
    for (t = 0; t < threads; t++) {
        rc = pthread_join(pthreads[t], NULL);
        if (rc) { printf("ERORR return code is %d\n", rc); exit(-1); }
    }

    /* Print result and release storage */
    printf ("Sum =  %f \n", data.sum);
    free (a);
    free (b);
    return 0;
}
